{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 图像人脸检测\n",
    "\n",
    "## 实验目的：\n",
    "本实验将实现用opencv自带的人脸库对读取的图片进行人脸检测并标注人脸框。\n",
    "- 能使用imread方法读取图片；\n",
    "- 能使用cvtColor方法转换图像色彩；\n",
    "- 能使用CascadeClassifier方法加载HaarCascade模型检测图片人脸；\n",
    "- 能使用rectangle方法绘制人脸矩形框。\n",
    "\n",
    "## 实验内容：\n",
    "- HaarCascade人脸检测库的调用实验；\n",
    "- 级联分类器的使用；\n",
    "- 使用opencv的人脸库进行图像人脸检测实验。\n"
   ],
   "id": "114a8f5b9908a9bf"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. 了解人脸检测\n",
    "人脸检测在日常生活中受到广泛的应用，人脸检测就是在一幅图像中找出所有人脸的位置。\n",
    "\n",
    "通过OpenCV内置的人脸检测模型，对画面中的人脸绘制出人脸所在位置。\n",
    "\n",
    "通过人脸检测后再进行人脸识别就可以实现日常生活中应用。例如：扫脸解锁手机，扫脸支付，扫脸开门。\n",
    "\n",
    "<img src = './img/人脸检测1.png' width = 600 height = 500>\n",
    "\n",
    "- 人脸变成了一个人非常重要的生物信息。\n"
   ],
   "id": "4ba9894a63e0098b"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. 图像人脸检测\n",
    "读入图片，检测人脸，并在图片中标识人脸所在的矩形区域。"
   ],
   "id": "dd90b51130e6a68"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.1 导入cv2\n",
    "\n",
    "`python-opencv` 在python中的包名称叫做 cv2\n",
    "- `cv2`实现图像处理和计算机视觉方面的很多通用算法。"
   ],
   "id": "a79b9e5bc704443a"
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "import cv2"
   ],
   "id": "eb2b408bd87de5e6"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.2 读入图片\n",
    "- `cv2.imread(filepath)`：读取彩色图像，图像是按照BGR像素存储\n",
    "\n",
    "  参数说明：\n",
    "    - `filepath`要读入图片的完整路径"
   ],
   "id": "fc67a4a99c1f4476"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`img_path`变量定义图片路径，通过`cv2.imread`读取图片，将返回值赋值给`img`"
   ],
   "id": "33ae1dce86bdeefe"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<font color=red size=3>动手练习1</font>\n",
    "\n",
    "在`<1>`处，请用`cv2.imread()`来读取`img_path`图片。"
   ],
   "id": "3a8c8540c6c204f3"
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[[176, 177, 181],\n",
       "        [176, 177, 181],\n",
       "        [176, 177, 181],\n",
       "        ...,\n",
       "        [174, 177, 181],\n",
       "        [174, 177, 181],\n",
       "        [173, 176, 180]],\n",
       "\n",
       "       [[176, 177, 181],\n",
       "        [176, 177, 181],\n",
       "        [176, 177, 181],\n",
       "        ...,\n",
       "        [174, 177, 181],\n",
       "        [174, 177, 181],\n",
       "        [173, 176, 180]],\n",
       "\n",
       "       [[176, 177, 181],\n",
       "        [176, 177, 181],\n",
       "        [176, 177, 181],\n",
       "        ...,\n",
       "        [174, 177, 181],\n",
       "        [174, 177, 181],\n",
       "        [173, 176, 180]],\n",
       "\n",
       "       ...,\n",
       "\n",
       "       [[180, 180, 180],\n",
       "        [182, 182, 182],\n",
       "        [184, 184, 184],\n",
       "        ...,\n",
       "        [133, 138, 142],\n",
       "        [138, 141, 145],\n",
       "        [139, 142, 146]],\n",
       "\n",
       "       [[183, 183, 183],\n",
       "        [184, 184, 184],\n",
       "        [186, 186, 186],\n",
       "        ...,\n",
       "        [133, 138, 142],\n",
       "        [138, 141, 145],\n",
       "        [139, 142, 146]],\n",
       "\n",
       "       [[183, 183, 183],\n",
       "        [184, 184, 184],\n",
       "        [186, 186, 186],\n",
       "        ...,\n",
       "        [133, 138, 142],\n",
       "        [138, 141, 145],\n",
       "        [139, 142, 146]]], dtype=uint8)"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 设置图片路径\n",
    "img_path = 'img/eyes.jpg'\n",
    "# 载入带有人脸的图片\n",
    "img_face = cv2.imread(img_path)\n",
    "img_face"
   ],
   "id": "bec5bd0156b13c8e"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "填写完成后执行以下代码，`if`语句用于判断图片是否正确读入，若未正确读入图片则`print`输出，反之说明填写正确。"
   ],
   "id": "f5ea9188e380260e"
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "scrolled": true,
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[[176 177 181]\n",
      "  [176 177 181]\n",
      "  [176 177 181]\n",
      "  ...\n",
      "  [174 177 181]\n",
      "  [174 177 181]\n",
      "  [173 176 180]]\n",
      "\n",
      " [[176 177 181]\n",
      "  [176 177 181]\n",
      "  [176 177 181]\n",
      "  ...\n",
      "  [174 177 181]\n",
      "  [174 177 181]\n",
      "  [173 176 180]]\n",
      "\n",
      " [[176 177 181]\n",
      "  [176 177 181]\n",
      "  [176 177 181]\n",
      "  ...\n",
      "  [174 177 181]\n",
      "  [174 177 181]\n",
      "  [173 176 180]]\n",
      "\n",
      " ...\n",
      "\n",
      " [[180 180 180]\n",
      "  [182 182 182]\n",
      "  [184 184 184]\n",
      "  ...\n",
      "  [133 138 142]\n",
      "  [138 141 145]\n",
      "  [139 142 146]]\n",
      "\n",
      " [[183 183 183]\n",
      "  [184 184 184]\n",
      "  [186 186 186]\n",
      "  ...\n",
      "  [133 138 142]\n",
      "  [138 141 145]\n",
      "  [139 142 146]]\n",
      "\n",
      " [[183 183 183]\n",
      "  [184 184 184]\n",
      "  [186 186 186]\n",
      "  ...\n",
      "  [133 138 142]\n",
      "  [138 141 145]\n",
      "  [139 142 146]]]\n"
     ]
    }
   ],
   "source": [
    "if img_face is None:\n",
    "    # 判断图片是否读入正确\n",
    "    print(\"ERROR：请检查图片路径\")\n",
    "else:\n",
    "    print(img_face)"
   ],
   "id": "57ffdf8c46a557b8"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.3 图像转换\n",
    "把`BGR`彩色图像转换成`GRAY`灰度图需要使用`cvtColor`函数进行转换\n",
    "- `cv2.cvtColor(src, code)`：\n",
    "\n",
    "  参数说明：\n",
    "  - `src`：它是要更改其色彩空间的图像。\n",
    "  - `code`：它是色彩空间转换代码。"
   ],
   "id": "b14e229b360461d4"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<font color=red size=3>动手练习2</font>\n",
    "\n",
    "在`<1>`处，请用`cv2.cvtColor()`来将彩色图像`img`通过`cv2.COLOR_BGR2GRAY`转化成灰度图`gray`。"
   ],
   "id": "3d5c7f2548a324c"
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[178 178 178 ... 178 178 177]\n",
      " [178 178 178 ... 178 178 177]\n",
      " [178 178 178 ... 178 178 177]\n",
      " ...\n",
      " [180 182 184 ... 139 142 143]\n",
      " [183 184 186 ... 139 142 143]\n",
      " [183 184 186 ... 139 142 143]]\n"
     ]
    }
   ],
   "source": [
    "# 将彩色图片转换为灰度图\n",
    "# <1>\n",
    "result_cov = cv2.cvtColor(img_face, cv2.COLOR_BGR2GRAY)\n",
    "if result_cov is None:\n",
    "    print(\"ERROR：转换错误\")\n",
    "else:\n",
    "    print(result_cov)\n",
    "    cv2.imshow(\"result_cov\", result_cov)\n",
    "    cv2.waitKey(0)"
   ],
   "id": "25d5d09442ad10cc"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "填写完成后执行以下代码，输出如下所示，说明填写正确。\n",
    "\n",
    "```python\n",
    "gray\n",
    "```\n",
    "```\n",
    "array([[142, 142, 142, ..., 147, 147, 147],\n",
    "       [142, 142, 142, ..., 147, 147, 147],\n",
    "       [142, 142, 142, ..., 147, 147, 147],\n",
    "       ...,\n",
    "       [112, 112, 113, ..., 110, 110, 111],\n",
    "       [113, 113, 114, ..., 112, 112, 112],\n",
    "       [113, 113, 114, ..., 109, 109, 109]], dtype=uint8)\n",
    "\n",
    "```"
   ],
   "id": "f7ea2f98724c1fdb"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.4 载入人脸检测的Cascade模型\n",
    "`CascadeClassifier`**级联分类器的使用方法**\n",
    "\n",
    "通过`HaarCascade`模型，输入图片，就可以获取人脸所在区域的矩形位置。\n",
    "\n",
    "模型的使用方法简单，首先载入对应的`HaarCascade`文件，文件格式为xml。这里已经将文件下载到了`haar`文件夹下， 可以通过相对路径进行引用。\n",
    "```\n",
    "├── haar\n",
    "    └── haarcascade_frontalface_default.xml\n",
    "```   \n",
    "\n",
    "<img src = './img/人脸检测3.png'  width = 600 height = 500></img>\n",
    "    \n"
   ],
   "id": "7a9a315d49118dd1"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "载入人脸检测的Cascade模型，就是在`cv2.CascadeClassifier()`中传入对应`HaarCascade`文件，即`haarcascade_frontalface_default.xml`\n",
    "\n",
    "`FaceCascade`为分类器对象，以供后续调用使用。"
   ],
   "id": "f116387966035359"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<font color=red size=3>动手练习3</font>\n",
    "\n",
    "在`<1>`处，请用`cv2.CascadeClassifier`来载入人脸检测的Cascade模型`./haar/haarcascade_frontalface_default.xml`。"
   ],
   "id": "9fa19911a3d677b4"
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 载入人脸检测的Cascade模型\n",
    "# <1>\n",
    "face_cascade = cv2.CascadeClassifier('./haar/haarcascade_frontalface_default.xml')\n",
    "eye_cascade = cv2.CascadeClassifier('./haar/haarcascade_eye.xml')\n",
    "if face_cascade.empty() or eye_cascade.empty():\n",
    "    print('--(!)Error loading face cascade')"
   ],
   "id": "b2f887d8944bdee0"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "填写完成后执行以下代码，输出结果类似为`<CascadeClassifier 0x7f675be730>`的级联分类器实例对象，说明填写正确。"
   ],
   "id": "d327434932b23e20"
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "< cv2.CascadeClassifier 00000199A3CF36F0>"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "face_cascade"
   ],
   "id": "ef2d0667aa263590"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**知识补充**\n",
    "\n",
    "**关于HaarCascade**\n",
    "\n",
    "OpenCV中人脸识别是通过Haar特征的级联分类器实现，在这里暂不涉及太多底层算法原理部分的讲解。\n",
    "\n",
    "级联分类器原理说明参考：https://blog.csdn.net/wutao1530663/article/details/78294349\n",
    "\n",
    "OpenCV里面实际上有很多预先训练好的HaarCascade模型(XML文件) , 例如正脸检测， 眼睛检测， 全身检测，下半身检测等。\n",
    "\n",
    "<img src = './img/人脸检测2.png'  width = 800 height = 600>\n",
    "\n",
    "在OpenCV的两个代码仓库里面都有。\n",
    "\n",
    "数据1:https://github.com/opencv/opencv/tree/master/data/haarcascades\n",
    "\n",
    "数据2:https://github.com/opencv/opencv_contrib/tree/246ea8f3bdf174a2aad6216c2601e3a93bf75c29/modules/face/data/cascades\n"
   ],
   "id": "97f863da284a9aad"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.5 检测画面中的人脸\n",
    "\n",
    "将图片的灰度图传入到这个`FaceCascade`模型中，进行人脸检测。\n",
    "\n",
    "- `FaceCascade.detectMultiScale(image, scaleFactor, minNeighbors)`：分类器对象调用参数调节\n",
    "\n",
    "  参数说明：\n",
    "  - `image`：输入图像\n",
    "  - `scaleFactor`：每次缩小图像的比例，默认是1.1\n",
    "  - `minNeighbors`：匹配成功所需要的周围矩形框的数目，每一个特征匹配到的区域都是一个矩形框，只有多个矩形框同时存在的时候，才认为是匹配成功，比如人脸匹配，默认值是3。\n"
   ],
   "id": "d9a7cfee364a19e0"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "使用`FaceCascade.detectMultiScale`分类器调整参数，输入转换好要检测的灰度图，设置图像比例为1.1，需要有5个矩形框同在才认为匹配成功。"
   ],
   "id": "e5fef8559555e753"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<font color=red size=3>动手练习4</font>\n",
    "\n",
    "1. 在`<>`处，请用`FaceCascade.detectMultiScale()`来检测画面中的人脸。传入的参数`image`为`gray`,scaleFactor为`1.1`，`minNeighbors`为5"
   ],
   "id": "cd3b6e7382af87b4"
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 检测图像中的人脸并返回绘制矩形框的值\n",
    "faces = face_cascade.detectMultiScale(img_face, 1.3, 5)"
   ],
   "id": "6cd7a9bad4245c89"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "填写完成后执行以下代码，输出结果类似为`array([[ 97, 141, 500, 500]], dtype=int32)`的人脸识别结果区域，说明填写正确。"
   ],
   "id": "8ed966cde52bd74c"
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 38,  59, 214, 214],\n",
       "       [342,  63, 211, 211]])"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "faces"
   ],
   "id": "87a5b5490f69500e"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "返回值`faces`是人脸所在区域的ROI数组，是识别到的人脸矩形框。例如：[(x1, y1, w1, h1), (x2, y2, w2, h2)]\n",
    "\n",
    "- 其中`[x, y, w, h]`：\n",
    "\n",
    "  分别是\n",
    "    - (x, y): 左上角坐标值\n",
    "    - w: 人脸矩形区域的宽度\n",
    "    - h: 人脸矩形区域的高度"
   ],
   "id": "4f3193e7235fd9f2"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**知识补充**\n",
    "\n",
    "<img src = './img/人脸检测4.png'  width = 600 height = 400>\n",
    "\n",
    "ROI 的全称是Region Of Interest , 用于表示在画面的子区域。整个画面的原点（0,0） ， 在整个画面中的左上角。 ROI 本质上是Tuple类型的数据，其中（x,y）代表人脸所在矩形区域的左上角坐标， w 代表矩形的宽度， h代表矩形的高度。\n",
    "\n"
   ],
   "id": "96322f4927276849"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.6 绘制矩形框\n",
    "\n",
    "- `cv2.rectangle(img，pt1，pt2，color, thickness)`：这个函数的作用是在图像上绘制一个简单的矩形。\n",
    "\n",
    "  参数说明：\n",
    "  - `img`：图片\n",
    "  - `pt1`：矩形的左上角顶点\n",
    "  - `pt2`：与pt1相反的矩形的顶点，即右下角顶点\n",
    "  - `color`：矩形的颜色或亮度（灰度图像）。\n",
    "  - `thickness`：它是矩形边框线的粗细像素"
   ],
   "id": "b6925633a314b27b"
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 遍历返回的face数组\n",
    "for face in faces:\n",
    "    # 解析tuple类型的face位置数据\n",
    "    # (x, y): 左上角坐标值\n",
    "    # w: 人脸矩形区域的宽度\n",
    "    # h: 人脸矩形区域的高度\n",
    "    (x, y, w, h) = face\n",
    "    # 在原彩图上绘制矩形\n",
    "    cv2.rectangle(img_face, (x, y), (x+w, y+h), (0, 255, 0), 4)"
   ],
   "id": "5d268c21bd1f287a"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.7 创建显示窗口\n",
    "\n",
    "`cv2.namedWindow(winname, flags)`：构建视频的窗口，用于放置图片\n",
    "\n",
    "参数说明：\n",
    "- `winname`：表示窗口的名字,可用作窗口标识符的窗口名称。\n",
    "- `flags`：用于设置窗口的属性，常用属性如下\n",
    "  - `WINDOW_NORMAL`：可以调整大小窗口\n",
    "  - `WINDOW_KEEPRATIO`：保持图像比例\n",
    "  - `WINDOW_GUI_EXPANDED`：绘制一个新的增强GUI窗口"
   ],
   "id": "c990624833097b4d"
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 创建一个窗口 名字叫做Face\n",
    "cv2.namedWindow('Face',flags=cv2.WINDOW_NORMAL | cv2.WINDOW_KEEPRATIO | cv2.WINDOW_GUI_EXPANDED)\n",
    "cv2.resizeWindow(\"Face\", 1920, 1080)"
   ],
   "id": "17b6e3d9293b2eee"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.8 显示图片\n",
    "\n",
    "- `cv2.imshow(winname, mat)`函数可以在窗口中显示图像。\n",
    "\n",
    "  参数\n",
    "  - `winname`：窗口名称（也就是我们对话框的名称），它是一个字符串类型。\n",
    "  - `mat`：是每一帧的画面图像。可以创建任意数量的窗口，但必须使用不同的窗口名称。\n",
    "         \n",
    "- `cv2.waitKey`：`waitkey`控制着`imshow`的持续时间，\n",
    "  - 当`imshow`之后不跟`waitkey`时，相当于没有给`imshow`提供时间展示图像，只会有一个空窗口一闪而过。\n",
    "  - `cv2.waitKey(100)`表示窗口中显示图像时间为100毫秒\n",
    "  - `cv2.imshow`之后一定要跟`cv2.waitKey`函数"
   ],
   "id": "5438c6f90ad4781d"
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "-1"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 在窗口Face上面展示图片img\n",
    "cv2.imshow('Face', img_face)\n",
    "# 等待任意按键按下\n",
    "cv2.waitKey(5000)"
   ],
   "id": "da56b906eb1c1ffd"
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 关闭所有的窗口\n",
    "cv2.destroyAllWindows()"
   ],
   "id": "a55568b87fb30fab"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### <font color=red>动手实验</font>\n",
    "\n",
    "按照以下要求完成实验：\n",
    "1. 在`<1>`处，读入图片`face1.jpg`，命名为`img`。\n",
    "2. 在`<2>`处，使用`cv2.cvtColor`函数进行转换，把`BGR`转换成`GRAY`灰度图\n",
    "3. 在`<3>`处，使用`cv2.CascadeClassifier`传入模型`haarcascade_frontalface_default.xml`\n",
    "4. 在`<4>`处，使用`FaceCascade.detectMultiScale`检测画面中的人脸，图像缩小的比例是1.1，匹配成功所需要的周围矩形框的数目为3。\n",
    "5. 在`<5>`处，使用`cv2.rectangle`和循环遍历进行绘制矩形框\n",
    "6. 在`<6>`处，使用`cv2.namedWindow`创建显示窗口命名为`Face`,属性设置为`可调整大小`，`保持图像比例`\n",
    "7. 在`<7>`处，使用`cv2.imshow`在窗口`Face`中显示图像`img`\n",
    "8. 在`<8>`处，设置`cv2.waitKey()`为200毫秒\n"
   ],
   "id": "600813d891829b78"
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "-1"
      ]
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import cv2\n",
    "# 完成代码\n",
    "#设置图片路径\n",
    "img_path1 = \"./face1.jpg\"\n",
    "#载入带有人脸的图片\n",
    "img = cv2.imread(img_path1)\n",
    "\n",
    "\n",
    "#将彩色图片转换为灰度图\n",
    "gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n",
    "\n",
    "\n",
    "#载入人脸检测的Cascade模型\n",
    "face_cascade = cv2.CascadeClassifier('./haar/haarcascade_frontalface_default.xml')\n",
    "eye_cascade = cv2.CascadeClassifier('./haar/haarcascade_eye.xml')\n",
    "if face_cascade.empty() or eye_cascade.empty():\n",
    "    print('--(!)Error loading face cascade')\n",
    "\n",
    "#检测画面中的人脸\n",
    "result_detect = face_cascade.detectMultiScale(gray, 1.1, 3)\n",
    "\n",
    "\n",
    "#遍历返回的face数组\n",
    "for face in result_detect:\n",
    "    #获取人脸的坐标\n",
    "    x,y,w,h = face\n",
    "    #在画面中画矩形\n",
    "    cv2.rectangle(img, (x,y), (x+w,y+h), (255,0,0), 2)\n",
    "\n",
    "\n",
    "#创建一个窗口 名字叫做Face\n",
    "cv2.namedWindow(\"Face\")\n",
    "\n",
    "\n",
    "#在窗口Face上面展示图片img\n",
    "cv2.imshow(\"Face\", img)\n",
    "#等待任意按键按下\n",
    "cv2.waitKey(0)"
   ],
   "id": "dc2863312ab9f8ec"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<font color=red>能够成功在图片`face1.jpg`中绘制出矩形框，则表示实验完成。"
   ],
   "id": "262e13aedb508610"
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "tags": []
   },
   "source": [
    "### 2.9 最后释放资源\n",
    "\n",
    "- `cv2.destroyAllWindows()`：用来删除所有窗口"
   ],
   "id": "30a40f47261e382a"
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 关闭所有的窗口\n",
    "cv2.destroyAllWindows()"
   ],
   "id": "20130a7006aaf976"
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "在OpenCV中，`detectMultiScale`是 Haar 级联分类器（如人脸、眼睛检测）的核心函数，用于在图像中多尺度检测目标。其参数调整直接影响检测的**精度**（是否漏检、假阳性）和**速度**，以下是详细的调参策略：\n",
    "\n",
    "\n",
    "### 函数参数原型\n",
    "```python\n",
    "detectMultiScale(\n",
    "    image,           # 输入灰度图（必须，级联分类器要求单通道）\n",
    "    scaleFactor=1.1, # 缩放因子（默认1.1）\n",
    "    minNeighbors=3,  # 最小邻域数（默认3）\n",
    "    flags=0,         # 旧版本兼容参数（现在基本不用）\n",
    "    minSize=None,    # 目标最小尺寸（(w,h)，默认None表示不限制）\n",
    "    maxSize=None     # 目标最大尺寸（(w,h)，默认None表示不限制）\n",
    ")\n",
    "```\n",
    "\n",
    "\n",
    "### 核心参数调参策略\n",
    "\n",
    "\n",
    "#### 1. `scaleFactor`（缩放因子）\n",
    "- **含义**：图像金字塔的缩放比例。每次搜索窗口会按该比例放大（或说图像按比例缩小），用于检测不同大小的目标。\n",
    "  例如：`scaleFactor=1.2` 表示每次缩放为前一次的1.2倍（即搜索窗口扩大20%）。\n",
    "- **调参原则**：\n",
    "  - 数值**越小**：缩放步长越小，能检测到更精细的尺度差异，漏检少，但计算量剧增（速度变慢）。\n",
    "  - 数值**越大**：缩放步长越大，速度快，但可能跳过某些尺度的目标（漏检多）。\n",
    "- **经验值**：\n",
    "  平衡速度和精度时，通常取 `1.1~1.4`。\n",
    "  - 对精度要求高（如小目标检测）：`1.1~1.2`；\n",
    "  - 对速度要求高（如实时检测）：`1.3~1.4`。\n",
    "\n",
    "\n",
    "#### 2. `minNeighbors`（最小邻域数）\n",
    "- **含义**：过滤假阳性的核心参数。每个候选目标需要被多少个“邻近检测框”包围才能被保留（类似投票机制）。\n",
    "  例如：`minNeighbors=2` 表示至少有2个邻近检测框支持时，才认为是有效目标。\n",
    "- **调参原则**：\n",
    "  - 数值**越小**：保留更多候选框，可能检测到更多目标，但假阳性（误检）会增多。\n",
    "  - 数值**越大**：过滤越严格，假阳性少，但可能漏检真实目标（尤其是特征不明显的目标）。\n",
    "- **经验值**：\n",
    "  根据目标特征明显程度调整：\n",
    "  - 特征明显的目标（如正面人脸）：`3~5`；\n",
    "  - 特征较模糊的目标（如侧脸、眼睛）：`2~3`（避免漏检）；\n",
    "  - 背景复杂、假阳性多的场景：适当增大（如`4~6`）。\n",
    "\n",
    "\n",
    "#### 3. `minSize` 和 `maxSize`（目标尺寸范围）\n",
    "- **含义**：限制目标的最小/最大宽高（单位：像素），过滤掉明显不符合尺寸的区域（如过大的背景块、过小的噪点）。\n",
    "  例如：检测眼睛时，`minSize=(20,20)`、`maxSize=(100,100)` 可排除过小的噪点和过大的区域。\n",
    "- **调参原则**：\n",
    "  - 必须根据**实际场景中目标的尺寸范围**设定（可通过先观察样本图像统计目标大小）。\n",
    "  - `minSize` 太小：会检测到大量小噪点（假阳性），增加计算量；\n",
    "  - `maxSize` 太大：会保留过大的背景区域（假阳性），降低效率。\n",
    "- **经验值**：\n",
    "  例如检测人脸时，`minSize=(50,50)`（排除过小区域），`maxSize=(300,300)`（根据图像分辨率调整，避免超过图像尺寸）。\n",
    "\n",
    "\n",
    "#### 4. `flags`（旧版本兼容参数）\n",
    "- 现代OpenCV（3.x+）中已基本废弃，无需调整（默认0即可）。\n",
    "\n",
    "\n",
    "### 调参步骤（实战流程）\n",
    "1. **先定尺寸范围**：通过观察样本图像，确定目标的大致 `minSize` 和 `maxSize`（优先过滤无效区域，提高速度）。\n",
    "2. **调整缩放因子**：从较大值（如1.4）开始测试，确保能快速看到初步结果；再逐步减小到1.2（平衡精度）。\n",
    "3. **优化邻域数**：先设较小值（如2），观察是否有大量假阳性；若假阳性多，逐步增大到3~5，直到假阳性可接受（同时确保真目标不被漏检）。\n",
    "4. **迭代验证**：用多个样本（不同角度、光照、背景）测试，根据漏检/误检情况微调参数。\n",
    "\n",
    "\n",
    "### 示例（针对眼睛检测）\n",
    "若你的代码 `result_detect = eye_cascade.detectMultiScale(gray, 1.2, 2)` 出现问题：\n",
    "- 若假阳性多（检测到非眼睛区域）：增大 `minNeighbors` 到3，或缩小 `maxSize`（如`maxSize=(80,80)`）。\n",
    "- 若漏检多（没检测到眼睛）：减小 `scaleFactor` 到1.1，或缩小 `minSize`（如`minSize=(15,15)`）。\n",
    "\n",
    "\n",
    "通过以上策略，可在精度和速度之间找到最优平衡，适配具体的检测场景。\n",
    "\n",
    "https://ucnfbuuez73w.feishu.cn/wiki/AymRwUYiJi3Et8kLB3tcEhL9n1c"
   ],
   "id": "8ae789fdb06e076b"
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-11-04T00:49:01.814348Z",
     "start_time": "2025-11-04T00:45:52.195788Z"
    }
   },
   "source": [
    "import cv2\n",
    "# 完成代码\n",
    "#设置图片路径\n",
    "# img_path1 = r\"C:\\Users\\Admin\\Desktop\\CV\\detect\\img\\humen_face.png\"\n",
    "img_path1 = r\"C:\\Users\\Admin\\Desktop\\CV\\detect\\face.jpg\"\n",
    "#载入带有人脸的图片\n",
    "img = cv2.imread(img_path1)\n",
    "\n",
    "#将彩色图片转换为灰度图\n",
    "gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n",
    "\n",
    "#载入人脸检测的Cascade模型\n",
    "# face_cascade = cv2.CascadeClassifier('./haar/haarcascade_frontalface_default.xml')\n",
    "eye_cascade = cv2.CascadeClassifier('./haar/haarcascade_eye.xml')\n",
    "if eye_cascade.empty():\n",
    "# if face_cascade.empty() or eye_cascade.empty():\n",
    "    print('--(!)Error loading face cascade')\n",
    "\n",
    "# 检测画面中的人脸\n",
    "result_detect = eye_cascade.detectMultiScale(\n",
    "    gray,           # 输入灰度图（必须，级联分类器要求单通道）\n",
    "    scaleFactor=1.1, # 缩放因子（默认1.1）\n",
    "    minNeighbors=3,  # 最小邻域数（默认3）\n",
    "    flags=0,         # 旧版本兼容参数（现在基本不用）\n",
    "    minSize=None,    # 目标最小尺寸（(w,h)，默认None表示不限制）\n",
    "    maxSize=None     # 目标最大尺寸（(w,h)，默认None表示不限制）\n",
    ")\n",
    "\n",
    "#遍历返回的face数组(里面没有置信度分数)（只有原点会宽高）\n",
    "for eyes in result_detect:\n",
    "    #获取人脸的坐标\n",
    "    x,y,w,h = eyes\n",
    "    #在画面中画矩形\n",
    "    cv2.rectangle(img, (x,y), (x+w,y+h), (255,0,0), 2)\n",
    "\n",
    "\n",
    "#创建一个窗口 名字叫做Face\n",
    "cv2.namedWindow(\"eyes\")\n",
    "\n",
    "\n",
    "#在窗口Face上面展示图片img\n",
    "cv2.imshow(\"eyes\", img)\n",
    "#等待任意按键按下\n",
    "cv2.waitKey(0)"
   ],
   "id": "5956f883567783c2",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "-1"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 1
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e5adebcd",
   "metadata": {},
   "outputs": [],
   "source": [
    "def custom_score(detect_fullbody, detect_eyes, img):\n",
    "    score = 0\n",
    "    # 奖励：人体和眼睛数量符合预期\n",
    "    if len(detect_fullbody) == 1 and len(detect_eyes) == 1:\n",
    "        score += 10  # 基础奖励分\n",
    "        # 进一步奖励：框位置大致合理（示例：人体框高度占图像高度的60%~80%）\n",
    "        img_h = img.shape[0]\n",
    "        body_h = detect_fullbody[0][3]\n",
    "        if 0.6 < body_h / img_h < 0.8:\n",
    "            score += 5\n",
    "\n",
    "    else:\n",
    "        # 惩罚：数量不符\n",
    "        score -= 10 * abs(len(detect_fullbody) - 1)  # 人体数量偏差的惩罚\n",
    "    return score"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8c831856",
   "metadata": {},
   "outputs": [],
   "source": [
    "import cv2\n",
    "# 完成代码\n",
    "#设置图片路径\n",
    "img_path1 = \"./fulbody.jpg\"\n",
    "#载入带有人脸的图片\n",
    "img = cv2.imread(img_path1)\n",
    "\n",
    "\n",
    "#将彩色图片转换为灰度图\n",
    "gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n",
    "\n",
    "\n",
    "#载入人脸检测的Cascade模型\n",
    "# fullbody_cascade = cv2.CascadeClassifier(r'.\\haar\\haarcascade_fullbody.xml')\n",
    "# eye_cascade = cv2.CascadeClassifier(r'.\\haar\\\\haarcascade_eye.xml')\n",
    "eye_cascade = cv2.CascadeClassifier(r'.\\haar\\haarcascade_frontalface_default.xml')\n",
    "if fullbody_cascade.empty() or eye_cascade.empty():\n",
    "    print('--(!)Error loading face cascade')\n",
    "\n",
    "#检测画面中的人脸,眼睛\n",
    "# result_detect = face_cascade.detectMultiScale(gray, 1.09, 2)\n",
    "for par1 in range(100,200,1):\n",
    "    custon_scores = []\n",
    "    par1 /=100\n",
    "    par1 = par1 + 0.01\n",
    "    result_detect_fullbody = fullbody_cascade.detectMultiScale(gray, par1, 2)\n",
    "    result_detect_eye = eye_cascade.detectMultiScale(gray, 1.08, 2)\n",
    "    # 用评价函数计算参数\n",
    "    score = custom_score(result_detect_fullbody,result_detect_eye,gray)\n",
    "    custon_scores.append(score)\n",
    "print(\"打印分数列表\",custon_scores)\n",
    "\n",
    "#遍历返回的face数组\n",
    "for fullbody in result_detect_fullbody:\n",
    "    x,y,w,h = fullbody\n",
    "    #在画面中画矩形\n",
    "    cv2.rectangle(img, (x,y), (x+w,y+h), (255,0,0), 2)\n",
    "for eyes in result_detect_eye:\n",
    "    x,y,w,h = eyes\n",
    "    #在画面中画矩形\n",
    "    cv2.rectangle(img, (x,y), (x+w,y+h), (255,0,0), 2)\n",
    "\n",
    "\n",
    "#在窗口Face上面展示图片img\n",
    "cv2.imshow(\"fullbody & eyes\", img)\n",
    "#等待任意按键按下\n",
    "cv2.waitKey(0)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "mycv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.20"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
